"""
Path is an object-oriented path to a file system node. It is responsible for
modeling the organization of a file system. It must NOT modify the file
system in any way--that task is delegated to Node instances (File and Folder)

A path segment has two properties {absolute|relative} and {node|leaf}

            absolute        relative
leaf        /a/b/c.txt      b/c.txt      
node        /a/b/           b/ 
"""

import fnmatch
import os.path
import shutil
import time

################################################################################
# Classes
################################################################################

class TPath(object):
    """
    Implementation of file system path object. A Path may be absolute or 
    relative. An absolute path is a unique location on the file system.
    """

    def __init__(self, parent, basename):
        """
        Initialize path with parent path and base name.
        
        Keyword arguments:
        parent -- POSIX path object of parent
        basename -- name of leaf
        """
        self.parent = parent
        self.basename = basename

    def __repr__(self):
        """
        Convert to string.
        """
        
        # convert parent to string
        parent = repr(self.parent)
        
        # use the native os folder path separator
        separator = os.path.sep

        if self.parent is None:
            # this is a relative path
            parent = ''
            separator = ''

        if parent.endswith(os.path.sep):
            parent = parent[:-1]

        return parent + separator + self.basename

    def __eq__(self, that):
        """
        Compare 'that' path to 'this' path for equality.
        
        Keyword arguments:
        that -- path instance to compare
        """
        return self.basename == that.basename and \
            self.parent() == that.parent

    def __ne__(self, that):
        """
        Compare 'that' to 'this' for inequality.
        
        Keyword arguments:
        that -- path instance to compare
        """
        return not self.__eq__(that)

    def is_abs(self):
        """
        Return true if this is an absolute path. Need to follow all parents to
        either root or relative path.
        """
        return repr(self).startswith(os.path.sep)

    def is_file(self):
        """return true if this path points to a file."""
        return os.path.isfile(repr(self))

    def is_folder(self):
        """return true if this path points to a folder."""
        return os.path.isdir(repr(self))

    def is_link(self):
        """return true if this path points to a symlink."""
        return os.path.islink(repr(self))

    def exists(self):
        """return true if this path exists on the file system."""
        return os.path.exists(repr(self))

# TODO move to Node
#    def delete(self):
#        """Delete this path (and all content) if it exists."""
#
#        if self.exists():
#            if self.is_folder():
#                # recursive delete on folder
#                shutil.rmtree(repr(self))
#            else:
#                os.remove(repr(self))

#    def touch_file(self):
#        """If path doesn't exist, create an empty file. If it does exist, 
#        update its utime."""
#        if not self.exists() or self.is_file():
#            handle = os.open(repr(self), os.O_WRONLY | os.O_CREAT, 0666)
#            os.close(handle)
#            os.utime(repr(self), None)
#
#    def touch_folder(self):
#        """If path doesn't exist, create an empty folder."""
#        if not self.exists():
#            os.makedirs(repr(self), mode=0755)
#        elif self.is_folder():
#            os.utime(repr(self), None)

    def join(self, fragment):
        """Join this path with a path fragment and return a new path."""
        fragment = make(fragment)
        joined = repr(self) + os.path.sep + repr(fragment)
        return make(joined)

    def slice(self, index):
        """Slices the path along the specified index. If the index > 0, slice
        counts from left; otherwise, it counts from right."""
        sep = os.path.sep
        # create a list of segments
        # /a/b/c/d becomes [a,b,c,d]
        segments = repr(self).split(sep)
        if index > 0:
            # slice from left
            segments = segments[index:]
        else:
            # slice from right
            length = len(segments)
            segments = segments[:length + index]
        slice_path = sep.join(segments)
        return make(slice_path)

    def fnmatch(self, pattern):
        """Returns true if self.name matches the given pattern.

        pattern - a filename pattern with wildcards, eg *.py
        """
        return fnmatch.fnmatch(repr(self), pattern)

class TRoot(TPath):
    """A singleton instance of a path that represents the root node."""
    ROOT_STRING = '/'

    def __init__(self):
        """Initialize Root path."""
        TPath.__init__(self, self, TRoot.ROOT_STRING)

    def __repr__(self):
        """Root is rendered."""
        return TRoot.ROOT_STRING

    def __eq__(self, that):
        return repr(that) == TRoot.ROOT_STRING

    def __ne__(self, that):
        return not self.__eq__(that)


################################################################################
# Shared instances
################################################################################

ROOT = TRoot()

################################################################################
# Module methods
################################################################################

def is_path(path):
    """return true if 'path' is an instance of TPath. This method is a 
    convenience used to allow strings to be passed in as paths and identified
    as such."""
    return hasattr(path, 'exists')

def make_path(parent_path, child_name, expandvars=True):
    """makes a new path with specified parent and child."""
    if not is_path(parent_path):
        parent_path = make(parent_path, expandvars)
    
    return TPath(parent_path, child_name)

def make(path='.', expandvars=True):
    """Makes a new path from the specified path string."""
    result = path

    if not is_path(path):
        
        if expandvars:
            path = os.path.expandvars(path)
        
        # this is not a path object so instantiate one
        # special cases:
        #    '.' : current folder
        #    '..': current folder parent
        #    '/' : root folder
        if path == os.path.sep:
            return ROOT
        elif path == '.':
            # query the current directory
            path = os.path.abspath(os.path.curdir)
        elif path == '..':
            # query the parent directory
            path = os.path.dirname(os.path.abspath(os.path.curdir))

        # split path into fragments
        fragments = path.split(os.path.sep)

        # assume parent is ROOT but let's confirm
        parent = ROOT

        # if fragments don't start with '', this is a relative path
        if fragments[0] != '':

            # assumption was wrong
            parent = None

        else:

            fragments = fragments[1:]

        for fragment in fragments:
            path = TPath(parent, fragment)
            parent = path

        result = parent
    return result

def make_leaf(path):
    """
    Makes a new leaf path.
    
    Keyword arguments:
    path -- POSIX string path
    """
    pass

def parent_folder(fso):
    """
    Return the absolute path for the parent folder of the specified file
    system object.
    
    """
    return os.path.dirname(os.path.abspath(fso))
    
def rename(path, newpath):
    """
    Rename (move) specified path to new path
    """
    shutil.move(str(path), str(newpath))
    
def rename_to_bak(path):
    """
    Rename (move) specified path to bak. This is used to make a backup of a
    file or folder that is going to be modified.    
    """
    backup_path = str(path) + "." + str(time.time()) + ".bak"
    rename(path, backup_path)